Return values. Use functions that return values instead of pointer parameters.
Do while. Use do while when possible, instead of while or for. For do while, the optimizer does not have to duplicate the loop condition in order to move code from within the loop to outside the loop.
Unions. Avoid unions that cause overlap between integer and floating point data types. The optimizer can not assign such fields to registers.
Local variables. Use local variables and void global variables. In C programs, declare any variable outside of a function as static, unless that variable is referenced by another source file. Minimizing the use of global variables increases optimization opportunities for the compiler.
Value parameters. Pass parameters by value instead of passing by reference (pointers) or using global variables. Reference parameters have the same performance-degrading effects as the use of pointers (see below).
Pointers and aliasing. Aliases occur when there are multiple ways to reference the same data object. For instance, when the address of a global variable is passed as a subprogram argument, it may be referenced either using its global name, or via the pointer. The compiler must be conservative when dealing with objects that may be aliased, for instance keeping them in memory instead of in registers, and carefully retaining the original source program order for possibly aliased references.
Pointers in particular tend to cause aliasing problems, since it is often impossible for the compiler to identify their target objects. Therefore, you can help the compiler avoid possible aliases by introducing local variables to store the values obtained from dereferenced pointers. Indirect operations and calls affect dereferenced values, but do not affect local variables. Therefore, local variables can be kept in registers. The following example shows how the proper placement of pointers and the elimination of aliasing produces better code.
len>10
(for instance because it is changed in another function before calling zero), *p++ = 0
will eventually modify len. Therefore, the compiler cannot place len
in a register for optimal performance. Instead, the compiler must load it from memory on each pass through the loop.Consider the following source code:
char a[10]; int len = 10; void zero() { char *p; for (p= a; p != a + len; ) *p++ = 0; }The generated assembly code looks like this:
#8 for (p = a; p!= a + len; ) *p++ = 0; move $2, $4 lw $3, len addu $24, $4, $3 beq $24 $4 $33 # a + len != p $32: sb $0, 0($2) # *p = 0 addu $2, $2, 1 # p++ lw $25, len addu $8, $4, $25 bne $8, $2, $32 # a + len != p $33:You can increase the efficiency of this example by using subscripts instead of pointers, or by using local variables to store unchanging values.
Using subscripts instead of pointers. Using subscripts in the procedure azero
(as shown below) eliminates aliasing. The compiler keeps the value of len
in a register, saving two instructions. It still uses a pointer to access a
efficiently, even though a pointer is not specified in the source code. For example, consider the following source code:
char a[10]; int len = 10; void azero() { int i; for ( i = 0; i != len; i++ ) a[i] = 0; }The generated assembly code looks like this:
for (i = 0; i != len; i++ ) a[i] = 0; move $2, $0 # i = 0 beq $4, 0, $37 # len != a la $5, a $36: sb $0, 0($5) # *a = 0 addu $2, $2, 1 # i++ addu $5, $5, 1 # a++ bne $2, $4, $36 # i != len $37:Using local variables. Using local (automatic) variables or formal arguments instead of static or global prevents aliasing and allows the compiler to allocated them in registers.
For example, in the following code fragment, the variables loc
and form
are likely to be more efficient than ext*
and stat*
.
extern int ext1; static int stat1; void p ( int form ) { extern int ext2; static int stat2; int loc; ... }Write straightforward code. For example, do not use ++ and - - operators within an expression. Using these operators for their values, rather than for their side-effects, often produces bad code.
Addresses. Avoid taking and passing addresses (& values). Using addresses creates aliases, makes the optimizer store variables from registers to their home storage locations, and significantly reduces optimization opportunities that would otherwise be performed by the compiler.
VARARG/STDARG. Avoid functions that take a variable number of arguments. The optimizer saves all parameter registers on entry to VARARG or STDARG functions. If you must use these functions, use the ANSI standard facilities of stdarg.h. These produce simpler code than the older version of varargs.h